#include "../include/Functions.h"
#include <iostream>
#include <iomanip>
#include <sstream>
#include <cstdio>
#include <cstdlib>
#include <cmath>

namespace CG {

//Function from: http://www.evl.uic.edu/aej/594/code/ogl.cpp
//Read in a textfile (GLSL program)
// we need to pass it as a string to the GLSL driver
char *textFileRead(const char *fn) {
    FILE *fp;
    char *content = NULL;
    int count=0;

    if (fn != NULL) {
        fp = fopen(fn,"rt");
        if (fp != NULL) {
            fseek(fp, 0, SEEK_END);
            count = ftell(fp);
            rewind(fp);
            if (count > 0) {
                content = (char *)malloc(sizeof(char) * (count+1));
                count = fread(content,sizeof(char),count,fp);
                content[count] = '\0';
            }
            fclose(fp);
        }
    }
    return content;
}

float *normalize(float v[3]) {
    float length = sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]);
    if(length == 0.0)
        return NULL;
    v[0] /= length;
    v[1] /= length;
    v[2] /= length;
    return v;
}

float *cross(float vec1[3], float vec2[3], float result[3]) {
    result[0] = (vec1[1]*vec2[2])-(vec1[2]*vec2[1]);
    result[1] = (vec1[2]*vec2[0])-(vec1[0]*vec2[2]);
    result[2] = (vec1[0]*vec2[1])-(vec1[1]*vec2[0]);
    return result;
}

vec3 cross(vec3 v1, vec3 v2) {
    vec3 result;
    result.x = (v1.y*v2.z)-(v1.z*v2.y);
    result.y = (v1.z*v2.x)-(v1.x*v2.z);
    result.z = (v1.x*v2.y)-(v1.y*v2.x);
    return result;
}

float dot(vec3 v1, vec3 v2) {
    return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z;
}

float normDot(vec3 v1, vec3 v2) {
    v1 = normalize(v1);
    v2 = normalize(v2);
    return dot(v1, v2);
}

vec3 normalize(vec3 v) {
    float length = sqrt(v.x*v.x + v.y*v.y + v.z*v.z);
    if(length == 0.0)
        return v;
    v.x /= length;
    v.y /= length;
    v.z /= length;
    return v;
}

vec3 add(vec3 v1, vec3 v2) {
    vec3 result;
    result.x = v1.x+v2.x;
    result.y = v1.y+v2.y;
    result.z = v1.z+v2.z;
    return result;
}

vec3 mult(vec3 v, float m) {
    v.x *= m;
    v.y *= m;
    v.z *= m;
    return v;
}

std::string numberToString(int num) {
    std::stringstream ss(std::stringstream::in | std::stringstream::out);
    std::string out;
    ss << num;
    out = ss.str();

    return out;
}

std::string numberToString(float num, int precision) {
    std::stringstream ss(std::stringstream::in | std::stringstream::out);
    std::string out;
    ss << std::fixed << std::setprecision(precision) << num;
    out = ss.str();

    return out;
}

GLuint loadTexture(std::string filename) {
    GLuint texID;
    sf::Image texture;
    if(!texture.loadFromFile(filename)) {
        //exit(-1);
        return NULL;
    }
    GLubyte *textureData = new GLubyte[texture.getSize().x*texture.getSize().y*3];
    for(int x = 0; x < texture.getSize().x; x++) {
        for(int y = 0; y < texture.getSize().y; y++) {
            textureData[(x*3)+(3*y*texture.getSize().x)] = texture.getPixel(x, y).r;
            textureData[(x*3)+(3*y*texture.getSize().x)+1] = texture.getPixel(x, y).g;
            textureData[(x*3)+(3*y*texture.getSize().x)+2] = texture.getPixel(x, y).b;
        }
    }
    //Send it to the GPU
    glGenTextures(1, &texID);
    glBindTexture(GL_TEXTURE_2D, texID);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
   // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
   // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    //glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, texture.getSize().x, texture.getSize().y, 0, GL_RGB, GL_UNSIGNED_BYTE, textureData);
    glGenerateMipmap(GL_TEXTURE_2D);
    delete[] textureData;
    return texID;
}

void setTextureUnit(GLuint shaderProg, std::string name, GLuint unit) {
    GLuint loc = glGetUniformLocation(shaderProg, name.c_str());
    glUniform1i(loc, unit);
}

void bindTexture(GLuint texID, GLenum activeTexture) {
    glActiveTexture(activeTexture);
    glBindTexture(GL_TEXTURE_2D, texID);
}

GLuint loadShaderSource(std::string filename, GLenum shaderType) {
    char *shaderSource;
    GLuint shader;

    shader = glCreateShader(shaderType); //Ask for a handle to a new shader
    shaderSource = CG::textFileRead(filename.c_str()); //Read the source code from file
    glShaderSource(shader, 1, (const char**)(&shaderSource),NULL); //Load the source code into the shader
    free(shaderSource); //Source code no longer needed

    return shader;
}

GLuint setupShader(std::string vertFilename, std::string fragFilename) {
    GLuint vShader = loadShaderSource(vertFilename, GL_VERTEX_SHADER);
    GLuint fShader = loadShaderSource(fragFilename, GL_FRAGMENT_SHADER);

    //Compile the shaders
    glCompileShader(vShader);
    glCompileShader(fShader);
    //Create shader program
    GLuint shaderProg = glCreateProgram();
    //Attach shaders
    glAttachShader(shaderProg, vShader);
    glAttachShader(shaderProg, fShader);
    //Link shader program
    glLinkProgram(shaderProg);
    return shaderProg;
}

GLfloat euclidDistance(vec3 p1, vec3 p2){
    float dx = p1.x - p2.x;
    float dy = p1.y - p2.y;
    float dz = p1.z - p2.z;

    return sqrt(dx*dx + dy*dy + dz*dz);
}

//Uses Rodrigues' rotation formula
vec3 rotateVector(vec3 v1, vec3 v2, float angle) {
    angle *= (M_PI/180.0);
    return add(add(mult(v1, cos(angle)), mult(cross(v2, v1), sin(angle))), mult(v2, normDot(v2, v1)*(1.0-cos(angle))));
}

}
